﻿using System;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
namespace MQRevo
{

    /// <summary>
    /// Results of trying to establish connection with the smartcard reader
    /// </summary>
    public enum ConnectCardReaderResult
    {
        /// <summary>
        /// Card reader is connected
        /// </summary>
        Success,
        /// <summary>
        /// Card reader is not connected
        /// </summary>
        NoReadersAvailable,
        /// <summary>
        /// Error connecting to card reader
        /// </summary>
        Error
    }

    internal enum AuthKeyType
    {
        TransportKey,
        Key_A,
        Key_B
    }

    /// <summary>
    /// Functions related to communication with the smartcard reader
    /// </summary>
    public static class CardComm
    {
        #region Variables & Constants
        // global variables=
        internal static bool isCardPresent;						// determines if card is present on reader
        /// <summary>
        /// Name of the card reader currently connected
        /// </summary>
        public static string cardReader;

        internal static byte[] cardType = new byte[2];
        internal static byte[] FELICA_212 = new byte[2] { 0xF0, 0x11 };
        internal static byte[] FELICA_424 = new byte[2] { 0xF0, 0x12 };
        internal static byte[] MIFARE_1K = new byte[2] { 0x00, 0x01 };
        internal static byte[] MIFARE_4K = new byte[2] { 0x00, 0x02 };        

        internal static int currCardType = -1;					// 0: Customer, 1: Staff, 2: Admin, -1: Empty
        internal static int currCardSex = -1;						// 0: Male, 1: Female
        internal static string prevCardID = string.Empty;
        internal static string currCardID = string.Empty;
        internal static byte[] currCardIDArr = new byte[4] { 0x00, 0x00, 0x00, 0x00 };

        /// <summary>
        /// holds details of last error encountered
        /// </summary>
        public static string lastErr = string.Empty;            

        // variables
        private static IntPtr hContext;
        private static IntPtr hCard;
        private static IntPtr activeProtocol = IntPtr.Zero;
        private static int sendBufferLength;
        private static int recvBufferLength;
        private static byte[] sendBuffer = new byte[255];
        private static byte[] recvBuffer = new byte[255];
        private static byte[] ReadersList;
        #endregion

        #region Card-Level Operations
        /// <summary>
        /// Establishes the resource manager context (the scope) with the card reader.
        /// </summary>
        /// <returns>A boolean value depending on whether it succeeds or fails.</returns>
        public static bool EstablishContext()
        {
            try
            {

                // parameters direction: [in], [in], [in], [out]
                long result = MSSmartCard.SCardEstablishContext(MSSmartCard.SCARD_SCOPE_USER, IntPtr.Zero, IntPtr.Zero, out hContext);
                if (result == MSSmartCard.SCARD_S_SUCCESS)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                lastErr = ex.Message + ": " + ex.InnerException;
                //MessageBox.Show(ex.Message);
                return false;
            }
        }

        /// <summary>
        ///  Closes an established resource manager context, freeing any resources allocated under that context, including SCARDHANDLE objects and memory allocated using the SCARD_AUTOALLOCATE length designator
        /// </summary>
        /// <returns>A boolean value depending on whether it succeeds or fails.</returns>
        public static bool ReleaseContext()
        {
            try
            {

                long result = MSSmartCard.SCardReleaseContext(hContext);
                if (result == MSSmartCard.SCARD_S_SUCCESS)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                lastErr = ex.Message + ": " + ex.InnerException;
                //MessageBox.Show(ex.Message);
                return false;
            }
        }

        /// <summary>
        /// Connects to the card reader and retrieves its name.
        /// </summary>
        /// <returns>ConnectCardReaderResult enumeration depending on whether it succeeds or fails.</returns>
        public static ConnectCardReaderResult ConnectCardReader()
        {
            uint pcchReaders = 0;				// must be -1 as the reader value is null	
            string readerGroup = string.Empty;	// must be null value to list all readers (regardless of group)

            try
            {

                long result = MSSmartCard.SCardListReaders(hContext, null, null, ref pcchReaders);
                if (result != MSSmartCard.SCARD_S_SUCCESS)
                {
                    cardReader = string.Empty;
                    return ConnectCardReaderResult.Error;
                }

                ReadersList = new byte[pcchReaders];
                // fill reader list
                result = MSSmartCard.SCardListReaders(hContext, null, ReadersList, ref pcchReaders);
                if (result == MSSmartCard.SCARD_S_SUCCESS)
                {

                    string rname = "";
                    int idx = 0;
                    int k = 0;
                    while (ReadersList[idx] != 0)
                    {

                        while (ReadersList[idx] != 0)
                        {
                            rname = rname + (char)ReadersList[idx];
                            idx = idx + 1;
                        }

                        //Add 1st reader name to list
                        if (k == 0)
                        {
                            cardReader = rname;
                        }
                        rname = "";
                        idx = idx + 1;
                        k = k + 1;
                    }
                    return ConnectCardReaderResult.Success;
                }
                else if (result == MSSmartCard.SCARD_E_NO_READERS_AVAILABLE)
                {
                    cardReader = string.Empty;
                    return ConnectCardReaderResult.NoReadersAvailable;
                }
                else
                {
                    cardReader = string.Empty;
                    return ConnectCardReaderResult.Error;
                }


            }
            catch (Exception ex)
            {

                lastErr = ex.Message + ": " + ex.InnerException;
                //MessageBox.Show(ex.Message);
                return ConnectCardReaderResult.Error;
            }
        }

        /// <summary>
        /// Connects to the card and retrieves its ID.
        /// </summary>
        /// <returns>A boolean value depending on whether it succeeds or fails.</returns>
        internal static bool ConnectCard()
        {
            try
            {
                activeProtocol = IntPtr.Zero;
                // connect to card
                if (MSSmartCard.SCardConnect(hContext, cardReader, MSSmartCard.SCARD_SHARE_SHARED, MSSmartCard.SCARD_PROTOCOL_T0 | MSSmartCard.SCARD_PROTOCOL_T1,
                    ref hCard, ref activeProtocol) == MSSmartCard.SCARD_S_SUCCESS)
                {
                    // connection to card success (card is present)
                    isCardPresent = true;

                    // card type
                    string rdrName = cardReader + '\0';
                    uint ReaderLen = (uint)rdrName.Length + 1;
                    IntPtr State = IntPtr.Zero;
                    IntPtr Protocol = IntPtr.Zero;
                    uint ATRLen = 32;
                    byte[] ATR = new byte[32];
                    int retCode = MSSmartCard.SCardStatus(hCard, rdrName + '\0', ref ReaderLen, ref State, ref Protocol, ref ATR[0], ref ATRLen);
                    if (retCode == MSSmartCard.SCARD_S_SUCCESS)
                    {
                        //Check ATR
                        cardType[0] = ATR[13];
                        cardType[1] = ATR[14];

                        //if (cardType[0] == MIFARE_1K[0] && cardType[1]==MIFARE_1K[1]) Console.WriteLine("Mifare 1K");
                        //else if (cardType[0] == MIFARE_4K[0] && cardType[1] == MIFARE_4K[1]) Console.WriteLine("Mifare 4K");
                    }

                    // retrieve card serial number if necessary
                    if (string.IsNullOrEmpty(currCardID))
                    {
                        // set send buffer
                        ClearBuffers();
                        sendBuffer = new byte[] { 0xFF, 0xCA, 0x00, 0x00, 0x04 };
                        sendBuffer[0] = 0xFF;
                        sendBuffer[1] = 0xCA;
                        sendBuffer[2] = 0x00;
                        sendBuffer[3] = 0x00;
                        sendBuffer[4] = 0x04;
                        recvBuffer = new byte[255];
                        // set buffer lengths
                        sendBufferLength = sendBuffer.Length;
                        recvBufferLength = recvBuffer.Length;

                        SCARD_IO_REQUEST ioRequest = new SCARD_IO_REQUEST();
                        ioRequest.dwProtocol = MSSmartCard.SCARD_PROTOCOL_UNDEFINED;
                        ioRequest.cbPciLength = 8; // cbPciLength = System.Runtime.InteropServices.Marshal.SizeOf(ioRequest) = 8

                        // Card Transmit
                        // parameters direction: [in], [in], [in], [in], [in & out], [out], [in & [out]
                        int result = MSSmartCard.SCardTransmit(hCard, ref ioRequest, ref sendBuffer[0], sendBufferLength, ref ioRequest, ref recvBuffer[0], ref recvBufferLength);

                        // send data
                        if (result == 0)
                        {
                            // do not check for Get UID response code as the incoming buffer only contains the card ID
                            currCardIDArr[0] = recvBuffer[0];
                            currCardIDArr[1] = recvBuffer[1];
                            currCardIDArr[2] = recvBuffer[2];
                            currCardIDArr[3] = recvBuffer[3];

                            currCardID = recvBuffer[0].ToString("X2") + recvBuffer[1].ToString("X2") + recvBuffer[2].ToString("X2") + recvBuffer[3].ToString("X2");
                            // prevents retrival of card ID at the next poll even though the same card is connected	
                            if (string.IsNullOrEmpty(prevCardID))
                                prevCardID = currCardID;
                            return true;
                        }
                        else
                        {
                            // send data error
                            currCardID = string.Empty;
                            return false;
                        }
                    }
                    else
                        return true;
                }
                else
                {
                    // connection to card failure (card not present)
                    isCardPresent = false;
                    currCardID = string.Empty;
                    return false;
                }
            }
            catch (Exception ex)
            {

                lastErr = ex.Message + ": " + ex.InnerException;
                //MessageBox.Show(ex.Message);
                return false;
            }
        }

        /// <summary>
        /// Disconnects from the card
        /// </summary>
        public static void DisconnectCard()
        {
            int result = MSSmartCard.SCardDisconnect(hContext, MSSmartCard.SCARD_LEAVE_CARD);
        }

        private static bool SendData()
        {
            // Transmits data to card for all operations.

            SCARD_IO_REQUEST ioRequest = new SCARD_IO_REQUEST();
            ioRequest.dwProtocol = MSSmartCard.SCARD_PROTOCOL_UNDEFINED;
            ioRequest.cbPciLength = 8; // cbPciLength = System.Runtime.InteropServices.Marshal.SizeOf(ioRequest) = 8

            // Card Transmit
            // parameters direction: [in], [in], [in], [in], [in & out], [out], [in & [out]
            int result = MSSmartCard.SCardTransmit(hCard, ref ioRequest, ref sendBuffer[0], sendBufferLength, ref ioRequest, ref recvBuffer[0], ref recvBufferLength);


            return (result == MSSmartCard.SCARD_S_SUCCESS);
        }
        #endregion

        #region Block-Level Operations
        /// <summary>
        /// Authenticates with a specific block in the card.
        /// </summary>
        /// <param name="blockNum">Block Number (0 ~ 63) to be authenticated.</param>
        /// <param name="key">Authentication key</param>
        /// <returns>A boolean value depending on whether it succeeds or fails.</returns>
        internal static bool AuthBlock(int blockNum, byte[] key)
        {
            if (!isCardPresent)
                return false;

            int keyLocation = 0x60;

            // Step 1: Load authentication keys into reader

            // Load Authentication Keys APDU Format (11 Bytes)
            // Class:	FF
            // INS:		82
            // P1:		Key Structure	0x00 = Key is loaded into reader volatile memory.
            // P2:		Key Number		0x00 ~ 0x01 = Key Location.
            // Lc:		06
            // Data In:	Key (6 Bytes)

            // set send buffer
            sendBuffer = new byte[255];
            ClearBuffers();
            sendBuffer[0] = 0xFF;
            sendBuffer[1] = 0x82;
            sendBuffer[2] = 0x00;
            sendBuffer[3] = 0x00;
            sendBuffer[4] = 0x06;
            for (int i = 0; i < 6; i++)
                sendBuffer[i + 5] = key[i];

            // set buffer lengths
            sendBufferLength = 11;
            recvBufferLength = 2;

            // send data
            if (SendData())
            {
                // check for Load Authentication Keys response code

                // Load Authentication Keys Response Format (2 Bytes)
                // SW1 SW2
                // Success:	90 00
                // Error:	63 00
                if (recvBuffer[0] == 0x90 && recvBuffer[1] == 0x00)
                {

                    // Load Authentication Keys success

                    // Step 2: Use authentication keys that are loaded into reader to do authentication with card

                    // Authentication APDU Format (10 Bytes)
                    // Class:	FF
                    // INS:		86
                    // P1:		00
                    // P2:		00
                    // Lc:		05
                    // Data In:	Authenticate Data Bytes (5 Bytes)
                    // --->		Byte1:	Version 0x01
                    //			Byte2:	0x00
                    //			Byte3:	Block Number
                    //			Byte4:	Key Type		0x60 = TYPE A Key. 0x61 = TYPE B Key.
                    //			Byte5:	Key Number		0x00 ~ 0x1F = Key Location.

                    // set send buffer
                    ClearBuffers();
                    sendBuffer[0] = 0xFF;
                    sendBuffer[1] = 0x86;
                    sendBuffer[2] = 0x00;
                    sendBuffer[3] = 0x00;
                    sendBuffer[4] = 0x05;
                    sendBuffer[5] = 0x01;
                    sendBuffer[6] = 0x00;
                    sendBuffer[7] = (byte)blockNum;
                    sendBuffer[8] = (byte)keyLocation;
                    sendBuffer[9] = 0x00;

                    // set buffer lengths
                    sendBufferLength = 10;
                    recvBufferLength = 2;

                    // send data
                    if (SendData())
                    {
                        // check for Authentication response code

                        // Authentication Response Format (2 Bytes)
                        // SW1 SW2
                        // Success:	90 00
                        // Error:	63 00
                        if (recvBuffer[0] == 0x90 && recvBuffer[1] == 0x00)
                        {
                            return true;
                        }
                        else
                        {
                            // Authentication error
                            return false;
                        }
                    }
                    else
                        // send data error
                        return false;
                }
                else
                    // Load Authentication Keys error
                    return false;
            }
            else
                // send data error
                return false;
        }

        /// <summary>
        /// Reads a specific block in the card.
        /// </summary>
        /// <param name="blockNum">Block Number (0 ~ 63) to be read.</param>
        /// <param name="blockData">Holds data that will be read from the block.</param>
        /// <param name="mode">
        /// 0 - Read Binary Block
        /// 1 - Read Value Block
        /// </param>
        /// <returns></returns>
        internal static bool ReadBlock(int blockNum, byte[] blockData, int mode)
        {
            ClearBuffers();

            if (mode == 0)
            {

                // 0 - Read Binary Block

                // Read Binary Block APDU Format (5 Bytes)
                // Class:	FF
                // INS:		B0
                // P1:		00
                // P2:		Block Number
                // Le:		Number of Bytes to Read (Value: Maximum 16 Bytes = 0x10)

                // set send buffer
                sendBuffer[0] = 0xFF;
                sendBuffer[1] = 0xB0;
                sendBuffer[2] = 0x00;
                sendBuffer[3] = (byte)blockNum;
                sendBuffer[4] = 0x10;	// 16

                // set buffer lengths
                sendBufferLength = 5;
                recvBufferLength = 18;	// 16 + 2

                // send data
                if (SendData())
                {
                    // check for Read Binary Block response code

                    // Read Binary Block Response Format (N + 2 Bytes)
                    // 0<=N<=16 SW1 SW2
                    // Success:	90 00
                    // Error:	63 00
                    // Since N = 16, then SW1 = 17th Byte, SW2 = 18th Byte
                    if (recvBuffer[16] == 0x90 && recvBuffer[17] == 0x00)
                    {
                        // copy contents of recvBuffer into blockData
                        for (int i = 0; i <= 15; i++)
                            blockData[i] = recvBuffer[i];
                        return true;
                    }
                    else
                    {
                        // Read Binary Block error
                        return false;
                    }
                }
                else
                    // send data error
                    return false;
            }
            else
            {

                // 1 - Read Value Block

                // Read Value Block APDU Format (5 Bytes)
                // Class:	FF
                // INS:		B1
                // P1:		00
                // P2:		Block Number
                // Le:		04

                // set send buffer
                sendBuffer[0] = 0xFF;
                sendBuffer[1] = 0xB1;
                sendBuffer[2] = 0x00;
                sendBuffer[3] = (byte)blockNum;
                sendBuffer[4] = 0x04;

                // set buffer lengths
                sendBufferLength = 5;
                recvBufferLength = 6;

                // send data
                if (SendData())
                {
                    // check for Read Value Block response code

                    // Read Value Block Response Format (6 Bytes)
                    // Value SW1 SW2
                    // Success:	90 00
                    // Error:	63 00
                    // Value is Signed long integer

                    if (recvBuffer[4] == 0x90 && recvBuffer[5] == 0x00)
                    {
                        // copy contents of recvBuffer into blockData
                        for (int i = 0; i < 4; i++)
                            blockData[i] = recvBuffer[i];
                        return true;
                    }
                    else
                    {
                        // Read Binary Block error
                        return false;
                    }
                }
                else
                    // send data error
                    return false;
            }
        }

        /// <summary>
        /// Writes to a specific block in the card.
        /// </summary>
        /// <param name="blockNum">Block Number (0 ~ 63) to be read.</param>
        /// <param name="blockData">
        /// Holds data that will be written to the block (Update Binary Block) OR
        /// value that will be used for value manipulation (Value Block Operation).
        /// </param>
        /// <param name="mode">
        /// 0 - Write Data			(Update Binary Block)
        ///	1 - Write Value			(Value Block Operation)
        ///	2 - Increment Value		(Value Block Operation)
        ///	3 - Decrement Value		(Value Block Operation)
        /// </param>
        /// <returns>A boolean value depending on whether it succeeds or fails.</returns>
        internal static bool WriteBlock(int blockNum, byte[] blockData, int mode)
        {
            ClearBuffers();

            if (mode == 0)
            {

                // 0 - Write Data  (Update Binary Block)

                // Update Binary Block APDU Format (21 Bytes)
                // Class:	FF
                // INS:		D6
                // P1:		00
                // P2:		Block Number
                // Lc:		Number of Bytes to Update (Value: Must be 16 Bytes)
                // Data In:	Block Data (16 Bytes)

                // set send buffer
                sendBuffer[0] = 0xFF;
                sendBuffer[1] = 0xD6;
                sendBuffer[2] = 0x00;
                sendBuffer[3] = (byte)blockNum;
                sendBuffer[4] = 0x10; // 16

                // copy contents of blockData into sendBuffer
                for (int i = 0; i <= 15; i++)
                    sendBuffer[i + 5] = blockData[i];

                // set buffer lengths
                sendBufferLength = 21;
                recvBufferLength = 2;

                // send data
                if (SendData())
                {
                    // check for Update Binary Block response code

                    // Update Binary Block Response Format (2 Bytes)
                    // SW1 SW2
                    // Success:	90 00
                    // Error:	63 00
                    if (recvBuffer[0] == 0x90 && recvBuffer[1] == 0x00)
                    {
                        return true;
                    }
                    else
                    {
                        // Update Binary Block error
                        return false;
                    }
                }
                else
                    // send data error
                    return false;
            }
            else
            {
                // Value Block Operation APDU Format (10 Bytes)
                // Class:	FF
                // INS:		D7
                // P1:		00
                // P2:		Block Number
                // Lc:		05
                // Data In:	VB_OP (1 Byte)		0x00 = Store. 0x01 = Increment. 0x02 = Decrement.
                //			VB_Value (4 Bytes)	Signed long integer.

                // set send buffer
                sendBuffer[0] = 0xFF;
                sendBuffer[1] = 0xD7;
                sendBuffer[2] = 0x00;
                sendBuffer[3] = (byte)blockNum;
                sendBuffer[4] = 0x05;

                if (mode == 1)
                {
                    // 1 - Write Value (Value Block Operation)
                    sendBuffer[5] = 0x00;
                }
                else if (mode == 2)
                {
                    // 2 - Increment Value (Value Block Operation)
                    sendBuffer[5] = 0x01;
                }
                else
                {
                    // 3 - Decrement Value (Value Block Operation)
                    sendBuffer[5] = 0x02;
                }

                // copy contents of blockData into sendBuffer
                int j=0;
                for (int i = 3; i >= 0; i--)
                {
                    sendBuffer[j + 6] = blockData[i];
                    j++;
                }

                // set buffer lengths
                sendBufferLength = 10;
                recvBufferLength = 2;

                // send data
                if (SendData())
                {
                    // check for Value Block Operation response code

                    // Value Block Operation Response Format (2 Bytes)
                    // SW1 SW2
                    // Success:	90 00
                    // Error:	63 00
                    if (recvBuffer[0] == 0x90 && recvBuffer[1] == 0x00)
                    {
                        return true;
                    }
                    else
                    {
                        // Value Block Operation error
                        return false;
                    }
                }
                else
                    // send data error
                    return false;
            }
        }

        /// <summary>
        /// Copies value from a source block to a destination block.
        /// </summary>
        /// <param name="srcBlockNum">Source Block Number where value is to be copied from.</param>
        /// <param name="desBlockNum">Destination Block Number where value is to be copied to.</param>
        /// <returns>A boolean value depending on whether it succeeds or fails.</returns>
        internal static bool RestoreBlock(int srcBlockNum, int desBlockNum)
        {
            ClearBuffers();

            // Restore Value Block APDU Format (7 Bytes)
            // Class:	FF
            // INS:		D7
            // P1:		00
            // P2:		Source Block Number
            // Lc:		02
            // Data In:	03
            //			Target Block Number

            // set send buffer
            sendBuffer[0] = 0xFF;
            sendBuffer[1] = 0xD7;
            sendBuffer[2] = 0x00;
            sendBuffer[3] = (byte)srcBlockNum;
            sendBuffer[4] = 0x02;
            sendBuffer[5] = 0x03;
            sendBuffer[6] = (byte)desBlockNum;

            // set buffer lengths
            sendBufferLength = 7;
            recvBufferLength = 2;

            // send data
            if (SendData())
            {
                // check for Restore Value Block response code

                // Restore Value Block Response Format (2 Bytes)
                // SW1 SW2
                // Success:	90 00
                // Error:	63 00
                if (recvBuffer[0] == 0x90 && recvBuffer[1] == 0x00)
                {
                    return true;
                }
                else
                {
                    // Value Block Operation error
                    return false;
                }
            }
            else
                // send data error
                return false;
        }
        #endregion

        #region Utility Functions
        private static void ClearBuffers()
        {
            // Resets both send and receive buffers to 0.
            for (int i = 0; i < sendBuffer.Length; i++)
            {
                sendBuffer[i] = 0x00;
            }
            for (int i = 0; i < recvBuffer.Length; i++)
            {
                recvBuffer[i] = 0x00;
            }
        }
        
        #endregion
    }
}
